package secondary.vo;

import jade.content.Concept;
import jade.content.ContentElement;
import jade.content.lang.Codec;
import jade.content.lang.sl.SLCodec;
import jade.content.onto.Ontology;
import jade.content.onto.basic.Action;
import jade.core.AID;
import jade.core.behaviours.CyclicBehaviour;
import jade.lang.acl.ACLMessage;
import jade.util.leap.ArrayList;
import jade.util.leap.HashMap;
import jade.util.leap.List;
import ontology.UMCourtLVocabulary;
import ontology.VOOntology;
import ontology.actions.general.AddNewResult;
import ontology.actions.general.AddNewTime;
import ontology.actions.general.GetCases;
import ontology.actions.general.PreSelect;
import ontology.actions.general.ReturnList;
import ontology.actions.general.ReturnParseCases;
import ontology.concepts.general.DBQueryRules;
import ontology.concepts.general.Result;
import ontology.concepts.vo.VOCase;
import ontology.concepts.vo.VOPreSelectRules;
import classes.UMCourtAgent;

public class TemplateRetrievalVO extends UMCourtAgent implements UMCourtLVocabulary 
{

	private Codec codec = new SLCodec();
	private Ontology ontology = VOOntology.getInstance();
	
	VOPreSelectRules psr = new VOPreSelectRules(); 
	private HashMap tasks = new HashMap();
	private HashMap rulesUsed = new HashMap(); //for each tasks, saves the last rules used so that they can be changed if another iteration is needed
	private HashMap addresses = new HashMap(); //for each task saves the AID of the agent that requested it
	private HashMap historicRules = new HashMap(); //<convID, ArrayList<rules>> for each task, save all the rules in all the iterations in order to avoid cycles (pode acontecer que para evitar quebrar a regra min cases quebre a regra max cases e vice versa e nesses casos o sistema entra em ciclo)
	private long pstime = 0;
	
	
	protected void setup() 
	{
		System.out.println(getDate()+ ": "+this.getLocalName()+" agent is now running...");
		registerAgent((String)getArguments()[0]);
		
		// Register language and ontology
	    getContentManager().registerLanguage(codec);
	    getContentManager().registerOntology(ontology);
		
	    this.addBehaviour(new receiveBehaviour());
	}
	
	public void sendNewTime(String id, long sTime)
	{
		AddNewTime ant = new AddNewTime();
		ant.setId(id);
		ant.setTime((int) (System.currentTimeMillis()-sTime));
		
		ACLMessage m = new ACLMessage(ACLMessage.REQUEST);
		m.setLanguage(codec.getName());
		m.setOntology(ontology.getName());
		m.setConversationId(""+System.currentTimeMillis());
		
		try 
		{
			getContentManager().fillContent(m, new Action(aids.EVALUATOR, ant));
			m.addReceiver(aids.EVALUATOR);
			send(m);
		}
		catch(Exception e){e.printStackTrace();}
	}
	
	public void sendNewResultMessage(Result r)
	{
		AddNewResult nr = new AddNewResult();
		nr.setNewResult(r);
		
		ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
		msg.setLanguage(codec.getName());
		msg.setOntology(ontology.getName());
		
		try 
		{
			getContentManager().fillContent(msg, new Action(aids.EVALUATOR, nr));
			msg.addReceiver(aids.EVALUATOR);
			send(msg);
		}
		catch (Exception ex) { ex.printStackTrace(); }
	}
	
	class receiveBehaviour extends CyclicBehaviour
	{
		@Override
		public void action() 
		{
			ACLMessage msg = null;
			while((msg = blockingReceive()) == null)
				;
	        try 
			{
	        	ContentElement content = getContentManager().extractContent(msg);
		        Concept action = ((Action)content).getAction();
         		
		        switch (msg.getPerformative()) 
		        {
		        case (ACLMessage.REQUEST):
			        if (action instanceof PreSelect)
			        {
			        	content = getContentManager().extractContent(msg);
			            PreSelect ps = (PreSelect)((Action)content).getAction();
			           
			            handlePreSelect(ps, msg);
			            break;
			        }
		        case (ACLMessage.INFORM):
		        	if (action instanceof ReturnParseCases)
		        	{
		        		content = getContentManager().extractContent(msg);
		        		ReturnParseCases rpc = (ReturnParseCases)((Action)content).getAction();
		        		
		        		//Send the result to the agent that originally requested it
	            		//Get the address of the agent
	            		AID aid = (AID) addresses.get(msg.getConversationId());
	            		addresses.remove(msg.getConversationId());
	            		
	            		myprintln("SUCCESS: Returning results to agent"+aid);
	            		
	            		ReturnList rl = new ReturnList();
	            		rl.setList(rpc.getList());
		            	
	            		//Return List of cases 
	            		ACLMessage reply = new ACLMessage(ACLMessage.INFORM);
			            reply.setLanguage(codec.getName());
			            reply.setOntology(ontology.getName());
			            reply.setConversationId(msg.getConversationId());
						
						try 
						{
							getContentManager().fillContent(reply, new Action(aid, rl));
							reply.addReceiver(aid);
							send(reply);
						}
						catch (Exception ex) { ex.printStackTrace(); }
	            		
		        	}
		        	if (action instanceof ReturnList)
		        	{
		        		sendNewTime("ps", pstime);

		        		content = getContentManager().extractContent(msg);
			            ReturnList rl = (ReturnList)((Action)content).getAction();
			            
			            List l = rl.getList();
			            if(l==null){
			            	l= new ArrayList();
			            }
			            myprintln("Received a pre-selection of "+l.size()+" cases");
			            
			            if (l.size() > psr.getMaxCases())
			            {
			            	myprintln("Rule violated: \"Max cases\". Deciding new pre-select rules...");
			            	Result res = new Result();
			            	res.setAction(PRE_SELECT);
			            	res.setRes(FAILURE);
			            	res.setResultCode(MAX_CASES);
			            	sendNewResultMessage(res);
			            	
			            	//retrieve the last rules used in this particular case...
			            	DBQueryRules ru = (DBQueryRules) rulesUsed.get(msg.getConversationId());
			            	String depth = ru.getDepth();
			            	if (depth.equals("contract-type") || depth.equals("norm"))
			            	{
			            		if (depth.equals("contract-type"))
			            			depth = "norm";
			            		else
			            			depth = "action";
					            
			            		DBQueryRules nr = new DBQueryRules();
			            		nr.setDepth(depth);
			            		myprintln("Changed search depth to: "+depth);
			            		
			            		//check if these rules haven't already been used in past iterations, to avoid cycles
			            		ArrayList hRules = (ArrayList) historicRules.get(msg.getConversationId());
			            		Boolean found = false;
			            		for (int i=0;i<hRules.size() & !found;i++)
			            		{
			            			DBQueryRules r = (DBQueryRules) hRules.get(i);
			            			if (r.getDepth().equals(depth))
			            				found = true;
			            		}
			            		
			            		if (found)
			            		{
			            			myprintln("WARNING: These rules have already been used for this particular task...");
			            			myprintln("WARNING: Continuing violating rule(s) \"Max cases\", \"Min cases\"  ");
			            			
			            			res = new Result();
					            	res.setAction(PRE_SELECT);
					            	res.setRes(FAILURE);
					            	res.setResultCode(CYCLE);
					            	sendNewResultMessage(res);
				            		
				            		//Send the result to the agent that originally requested it
				            		//Get the address of the agent
				            		AID aid = (AID) addresses.get(msg.getConversationId());
				            		addresses.remove(msg.getConversationId());
				            		
				            		//Return List of cases (forward msg)
				            		msg.removeReceiver((AID) msg.getAllIntendedReceiver().next());
				            		msg.addReceiver(aid);
				            		send(msg);
			            			
			            		}	
			            		else
			            		{
				            		//retrieve original request
				            		PreSelect ps = (PreSelect) tasks.get(msg.getConversationId());
				            		
				            		//formulate new pre-select request
				            		VOCase newCase = (VOCase) ps.getNewCase();
						            
						            GetCases<Integer> gcs = new GetCases<Integer>();
						            gcs.setR(nr);
						            gcs.setC(newCase);
						            
						            //save the new rules that will be used for this request
						            rulesUsed.remove(msg.getConversationId());
						            rulesUsed.put(msg.getConversationId(), nr);
						            
						            hRules.add(nr);
						            historicRules.remove(msg.getConversationId());
						            historicRules.put(msg.getConversationId(), hRules);
						            
						            ACLMessage m = new ACLMessage(ACLMessage.REQUEST);
									m.setLanguage(codec.getName());
									m.setOntology(ontology.getName());
									m.setConversationId(msg.getConversationId());
									
									try 
									{
										pstime = System.currentTimeMillis();
										getContentManager().fillContent(m, new Action(aids.DATABASE, gcs));
										m.addReceiver(aids.DATABASE);
										send(m);
										myprintln("Requesting for cases that match the rules: "+nr);
									}
									catch (Exception ex) { ex.printStackTrace(); }
								}
			            	}
			            	else 
			            	{
			            		myprintln("WARNING: Cannot narrow search space anymore, continuing violating rule \"Max cases\" ");
			            		
			            		res = new Result();
				            	res.setAction(PRE_SELECT);
				            	res.setRes(FAILURE);
				            	res.setResultCode(CANNOT_NARROW_SPACE);
				            	sendNewResultMessage(res);
				            	
			            		//Send the result to the agent that originally requested it
			            		//Get the address of the agent
			            		AID aid = (AID) addresses.get(msg.getConversationId());
			            		addresses.remove(msg.getConversationId());
			            		
			            		//Return List of cases (forward msg)
			            		msg.removeReceiver((AID) msg.getAllIntendedReceiver().next());
			            		msg.addReceiver(aid);
			            		send(msg);
			            	}
			            }
			            else if (l.size() < psr.getMinCases())
			            {
			            	myprintln("Rule violated: \"Min cases\". Deciding new pre-select rules...");
			            	
			            	Result res = new Result();
			            	res.setAction(PRE_SELECT);
			            	res.setRes(FAILURE);
			            	res.setResultCode(MIN_CASES);
			            	sendNewResultMessage(res);
			            	
			            	//retrieve rules used in this particular case...
			            	DBQueryRules ru = (DBQueryRules) rulesUsed.get(msg.getConversationId());
			            	String depth = ru.getDepth();
			            	
			            	if (depth.equals("action") || depth.equals("norm"))
			            	{
			            		if (depth.equals("norm"))
			            			depth = "contract-type";
			            		else
			            			depth = "norm";
					            
			            		DBQueryRules nr = new DBQueryRules();
			            		nr.setDepth(depth);
			            		myprintln("Changed search depth to: "+depth);
			            		
			            		//check if the newly selected rules haven't already been used in past iterations, to avoid cycles
			            		ArrayList hRules = (ArrayList) historicRules.get(msg.getConversationId());
			            		Boolean found = false;
			            		for (int i=0;i<hRules.size() & !found;i++)
			            		{
			            			DBQueryRules r = (DBQueryRules) hRules.get(i);
			            			if (r.getDepth().equals(depth))
			            				found = true;
			            		}
			            		
			            		if (found)
			            		{
			            			myprintln("WARNING: These rules have already been used for this particular task...");
			            			myprintln("WARNING: Continuing violating rule(s) \"Max cases\", \"Min cases\"  ");
				            		
			            			res = new Result();
					            	res.setAction(PRE_SELECT);
					            	res.setRes(FAILURE);
					            	res.setResultCode(CYCLE);
					            	sendNewResultMessage(res);
					            	
				            		//Send the result to the agent that originally requested it
				            		//Get the address of the agent
				            		AID aid = (AID) addresses.get(msg.getConversationId());
				            		addresses.remove(msg.getConversationId());
				            		
				            		//Return List of cases (forward msg)
				            		msg.removeReceiver((AID) msg.getAllIntendedReceiver().next());
				            		msg.addReceiver(aid);
				            		send(msg);
			            			
			            		}	
			            		else
			            		{
				            		//retrieve original request
				            		PreSelect ps = (PreSelect) tasks.get(msg.getConversationId());
				            		
				            		//formulate new pre-select request
				            		VOCase newCase = (VOCase) ps.getNewCase();
						            
						            GetCases<Integer> gcs = new GetCases<Integer>();
						            gcs.setR(nr);
						            gcs.setC(newCase);
						            
						            //save the new rules that will be used for this request
						            rulesUsed.remove(msg.getConversationId());
						            rulesUsed.put(msg.getConversationId(), nr);
						            
						            hRules.add(nr);
						            historicRules.remove(msg.getConversationId());
						            historicRules.put(msg.getConversationId(), hRules);
						            
						            ACLMessage m = new ACLMessage(ACLMessage.REQUEST);
									m.setLanguage(codec.getName());
									m.setOntology(ontology.getName());
									m.setConversationId(msg.getConversationId());
									
									try 
									{
										pstime = System.currentTimeMillis();
										getContentManager().fillContent(m, new Action(aids.DATABASE, gcs));
										m.addReceiver(aids.DATABASE);
										send(m);
										myprintln("Requesting for cases that match the rules: "+nr);
									}
									catch (Exception ex) { ex.printStackTrace(); }
			            		}
			            	}
			            	else
			            	{
			            		myprintln("WARNING: Cannot open search space anymore, continuing violating rule \"Min cases\" ");
			            		
			            		res = new Result();
				            	res.setAction(PRE_SELECT);
				            	res.setRes(FAILURE);
				            	res.setResultCode(CANNOT_OPEN_SPACE);
				            	sendNewResultMessage(res);
				            	
			            		//Send the result to the agent that originally requested it
			            		//Get the address of the agent
			            		AID aid = (AID) addresses.get(msg.getConversationId());
			            		addresses.remove(msg.getConversationId());
			            		
			            		//Return List of cases (forward msg)
			            		msg.removeReceiver((AID) msg.getAllIntendedReceiver().next());
			            		msg.addReceiver(aid);
			            		send(msg);
			            	}
			            }
			            else //all rules respected
			            {
			            	//Send the result to the agent that originally requested it
		            		//Get the address of the agent
		            		AID aid = (AID) addresses.get(msg.getConversationId());
		            		addresses.remove(msg.getConversationId());
		            		
		            		myprintln("SUCCESS: Returning results to agent"+aid);
		            		
		            		Result r = new Result();
			            	r.setAction(PRE_SELECT);
			            	r.setRes(SUCCESS);
			            	sendNewResultMessage(r);
			            	
		            		//Return List of cases (forward msg)
		            		msg.removeReceiver((AID) msg.getAllIntendedReceiver().next());
		            		msg.addReceiver(aid);
		            		send(msg);
			            }
		        	}
		        }
			}
	        catch(Exception e) {e.printStackTrace();}
			
		}

		private void handlePreSelect(PreSelect ps, ACLMessage msg) {

            VOCase newCase = (VOCase) ps.getNewCase();
            pstime = System.currentTimeMillis();
            System.out.println("New Pre-Selection task arrived: < "+msg.getConversationId()+", "+newCase.getID()+" >");
            tasks.put(msg.getConversationId(), ps);
            addresses.put(msg.getConversationId(), msg.getSender());
            
            if (ps.getMethod().equals("tr"))
            {
            	// #1 request cases that match the rules from the database
	            String depth = new String();
	            switch(psr.getDepth())
	            {
	            	case 2: depth = "contract-type"; break; 
	            	case 1: depth = "norm"; break; 
	            	case 0: depth = "action"; break;	
	            }
	            
	            trRequest(depth, newCase, msg.getConversationId());
            }
            
            if (ps.getMethod().equals("ar"))
            {
            	myprintln("ar request not implemented...");
            	//arRequest(newCase, msg.getConversationId());            	
            }
		}

		private void trRequest(String depth, VOCase newCase, String convId) {
			DBQueryRules r = new DBQueryRules();
            r.setDepth(depth);
            
            GetCases<Integer> gcs = new GetCases<Integer>();
            gcs.setR(r);
            gcs.setC(newCase);
            
            //save the DBQuery rules used
            rulesUsed.put(convId, r);
            ArrayList hRules = new ArrayList();
            hRules.add(r);
            historicRules.put(convId, hRules);
            
            ACLMessage m = new ACLMessage(ACLMessage.REQUEST);
			m.setLanguage(codec.getName());
			m.setOntology(ontology.getName());
			m.setConversationId(convId);
			
			try 
			{
				getContentManager().fillContent(m, new Action(aids.DATABASE, gcs));
				m.addReceiver(aids.DATABASE);
				send(m);
				myprintln("Requesting for cases that match the rules: "+r);
			}
			catch (Exception ex) { ex.printStackTrace(); }
		}
	}
}
